/***************************************************************************
  tag: The SourceWorks  Tue Sep 7 00:55:18 CEST 2010  EnumTypeInfo.hpp

                        EnumTypeInfo.hpp -  description
                           -------------------
    begin                : Tue September 07 2010
    copyright            : (C) 2010 The SourceWorks
    email                : peter@thesourceworks.com

 ***************************************************************************
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public                   *
 *   License as published by the Free Software Foundation;                 *
 *   version 2 of the License.                                             *
 *                                                                         *
 *   As a special exception, you may use this file as part of a free       *
 *   software library without restriction.  Specifically, if other files   *
 *   instantiate templates or use macros or inline functions from this     *
 *   file, or you compile this file and link it with other files to        *
 *   produce an executable, this file does not by itself cause the         *
 *   resulting executable to be covered by the GNU General Public          *
 *   License.  This exception does not however invalidate any other        *
 *   reasons why the executable file might be covered by the GNU General   *
 *   Public License.                                                       *
 *                                                                         *
 *   This library is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   Lesser General Public License for more details.                       *
 *                                                                         *
 *   You should have received a copy of the GNU General Public             *
 *   License along with this library; if not, write to the Free Software   *
 *   Foundation, Inc., 59 Temple Place,                                    *
 *   Suite 330, Boston, MA  02111-1307  USA                                *
 *                                                                         *
 ***************************************************************************/


#include "../rtt-config.h"
#include "Types.hpp"
#include "TemplateTypeInfo.hpp"
#include "TemplateConstructor.hpp"

namespace RTT
{
    namespace types
    {

        /**
         * Type information for Enum types for which they are convertible
         * to int.
         *
         * @see StructTypeInfo
         */
        template<class T>
        struct EnumTypeInfo: public TemplateTypeInfo<T, false>
        {
        protected:
            static int enum_to_int(T e)
            {
                return (int) e;
            }

            static T int_to_enum(int i)
            {
                return (T) i;
            }

            std::map<T,std::string> to_string;
            typedef std::map<T,std::string> MapType;
        public:
            EnumTypeInfo(std::string type) :
                TemplateTypeInfo<T, false> (type)
            {
            }

            bool installTypeInfoObject(TypeInfo* ti) {
                if (!Types()->type("int")) {
                    log(Error) << "Failed to register enum <-> int conversion because type int is not known in type system."<<endlog();
                    return false;
                } else {
                    TemplateTypeInfo<T,false>::installTypeInfoObject(ti);
                    Types()->type("int")->addConstructor(newConstructor(
                            &EnumTypeInfo<T>::enum_to_int, true));
                }
                ti->addConstructor( newConstructor( &EnumTypeInfo<T>::int_to_enum, true) );

                // Don't delete us, we're memory-managed.
                return false;
            }

            /**
             * Composition also checks if source is an int or string, and if so, converts
             * it to the enum of type T kept in result.
             * @param source a DataSource<int> or DataSource<string>, generated by decomposeType().
             * @param result An AssignableDataSource<T> that will get the new enum value.
             */
            virtual bool composeType(base::DataSourceBase::shared_ptr source,
                    base::DataSourceBase::shared_ptr result) const
            {
                // First, try a plain update.
                if (result->update(source.get()))
                    return true;
                // try conversion from int to enum:
                internal::DataSource<int>::shared_ptr ds =
                        internal::DataSource<int>::narrow( source.get() );
                if (ds)
                {
                    typename internal::AssignableDataSource<T>::shared_ptr menum =
                            internal::AssignableDataSource<T>::narrow( source.get() );
                    assert(menum);
                    menum->set( (T)ds->get() );
                    return true;
                }
                // try conversion from string to enum:
                internal::DataSource<std::string>::shared_ptr dss =
                        internal::DataSource<std::string>::narrow( source.get() );
                if (dss)
                {
                    typename internal::AssignableDataSource<T>::shared_ptr menum =
                            internal::AssignableDataSource<T>::narrow( result.get() );
                    assert(menum);
                    for( typename MapType::const_iterator it = to_string.begin(); it != to_string.end(); ++it)
                        if ( it->second == dss->get() ) {
                            menum->set( it->first );
                            return true;
                        }
                }
                // no conversion possible.
                return false;
            }

            /**
             * Converts the enum to a string in case source is an enum and the string
             * mapping is known. In case there is no string mapping, an int is returned.
             * The conversion the other way around is done by composeType()
             * @return a new DataSource containing an int or a string, null if
             * source is not an enum of this type.
             */
            virtual base::DataSourceBase::shared_ptr decomposeType(base::DataSourceBase::shared_ptr source) const
            {
                // Convert enum to string
                typename internal::DataSource<T>::shared_ptr ds = internal::DataSource<T>::narrow( source.get() );
                if ( ds )
                {
                    // if not available, just convert to int.
                    if ( to_string.count( ds->get() ) == 0 ) {
                        log(Warning) << "No enum-to-string mapping defined for enum " << this->getTypeName() <<". Converting to int."<<endlog();
                        return new internal::ValueDataSource<int>( ds->get() );
                    }
                    internal::ValueDataSource<std::string>::shared_ptr vds =  new internal::ValueDataSource<std::string>( to_string.find(ds->get())->second );
                    return vds;
                }
                // convert string to enum
                return base::DataSourceBase::shared_ptr();
            }
        };
    }
}
